Skip to content

fix(sandbox): give each Hermes sandbox its own OpenAI-compatible API port - #8577

Open
laitingsheng wants to merge 26 commits into
mainfrom
fix/hermes-api-port-per-sandbox
Open

fix(sandbox): give each Hermes sandbox its own OpenAI-compatible API port#8577
laitingsheng wants to merge 26 commits into
mainfrom
fix/hermes-api-port-per-sandbox

Conversation

@laitingsheng

@laitingsheng laitingsheng commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Summary

The Hermes OpenAI-compatible API port was a fixed host-wide 8642, so only one Hermes sandbox per host could own its host forward. A snapshot clone therefore received no inference forward, and gateway restart on the clone exited 1 with a forward recovery failure it could never repair. Each Hermes sandbox now allocates its own API port from 8642 through 8652, and every consumer resolves that port instead of the manifest default.

Related Issue

Fixes #8543

Changes

  • src/lib/core/ports.ts adds HERMES_API_PORT_RANGE_START/END and isHermesApiPort. 8642 becomes the range start rather than a host-wide reservation.
  • src/lib/onboard/hermes-api-port.ts is new: allocation, resolveOnboardHermesApiPort, resolveSandboxHermesApiPort, and retargetHermesApiPortInUrl. The port is published back to NEMOCLAW_HERMES_API_PORT so the sandbox-create environment, the registry row, and the host forward agree on one value without threading an argument through the onboarding entrypoint, matching how ensureAgentDashboardForward already propagates the dashboard port through CHAT_UI_URL. A registered sandbox keeps its recorded port: an explicit environment value is accepted only at the create, recreate, and created-sandbox registration boundaries, and every other consumer rejects a conflicting value before it mutates a host forward. isValidHermesApiPort accepts only the allocation range, so an out-of-range operator value is refused outright.
  • src/lib/onboard/dashboard-port.ts extracts findAvailablePortInRange so the dashboard and API allocators share one forward-list, registry, and host-bind view, and adds getRegistryOccupiedHermesApiPorts for the cross-gateway view.
  • The registry carries hermesApiPort, sanitised in src/lib/state/registry.ts and validated fail-closed in src/lib/state/gateway-registry.ts so a corrupt value cannot drop occupancy and hand out a colliding port.
  • src/lib/actions/sandbox/snapshot.ts allocates the clone's own port before any destructive step, so range exhaustion aborts before deleteSandboxForRestore removes a --force destination, and overrides the value the source-entry spread would otherwise inherit.
  • src/lib/actions/sandbox/forward-recovery.ts resolves manifest-declared forward ports against the sandbox's own ports. This fixes both halves of the reported failure: recovery no longer demands the manifest dashboard port from a sandbox that owns a different one, and it targets the sandbox's own API port.
  • agents/hermes/start.sh reads NEMOCLAW_HERMES_API_PORT and publishes the resolved value as a 0444 marker at /run/nemoclaw/hermes-api-port, on the root-separated path and on the same-uid path that OpenShell-managed and macOS VM startups take. A one-shot openshell sandbox exec does not inherit the supervisor environment, so agents/hermes/mcp-config-transaction.py, agents/hermes/plugin/__init__.py, and scripts/managed-gateway-control.py read that marker instead. Under privilege separation the marker is root-owned and the sandbox user cannot rewrite it; the transaction helper opens it with O_NOFOLLOW and checks ownership, mode, link count, and size before trusting it. In the same-uid topology the gateway already runs as the sandbox user, so the marker carries that same authority, and a sandbox that cannot publish an allocated port there refuses to start rather than leave its helpers probing a default port any process can bind.
  • src/lib/onboard/managed-startup/profile.ts declares the reserved range locally instead of importing core/ports. That module is bundled into the managed image and is also executed directly by node --experimental-strip-types, and neither path resolves a relative value import; src/lib/onboard/managed-startup-profile.test.ts pins the two declarations to the same values.
  • tools/mcp-tool-discovery-runtime/reviewed-runtime-bundle/managed-startup-image-runtime.bundle is regenerated for that change, and its pinned digest in test/mcp-tool-discovery-image-contract.test.ts is updated. bundle:reviewed:check reproduced the other three reviewed artefacts byte-for-byte, so only the managed-startup bundle moved.
  • src/lib/agent/onboard.ts passes sandboxName through the API-kind dashboard branch, which otherwise announced the manifest default for a Hermes sandbox that owns another port.
  • The sandbox agent rejection message, the onboarding ready summary, and scripts/install.sh resolve the sandbox's port instead of naming 8642, and the installer restores a registered Hermes forward rather than the manifest default.
  • The reserved-port guards in src/lib/onboard/preflight-ports.ts, src/lib/onboard/hermes-dashboard.ts, and the managed-startup profile validation cover the whole range rather than the single literal.
  • ci/source-architecture-budget.json raises four limits. Introducing one module adds edges to snapshot.ts fan-out, the src/lib/onboard root-file count, and core/ports.ts/state/registry.ts fan-in. Two other increases were avoidable and were removed instead: getSandboxHealthProbeUrl moved into forward-recovery.ts, which already owned that dependency, and snapshot.ts no longer imports core/ports.
  • Merged origin/main to keep the branch current. The merge was clean.

Type of Change

  • Code change (feature, bug fix, or refactor)
  • Code change with doc updates
  • Doc only (prose changes, no code sample modifications)
  • Doc only (includes code sample changes)

Quality Gates

  • Tests added or updated for changed behavior
  • Existing tests cover changed behavior — justification:
  • Tests not applicable — justification:
  • Docs updated for user-facing behavior changes
  • Docs not applicable — justification:
  • Sensitive paths changed (security, policy, credentials, preflight, onboarding, inference, runner, sandbox, or messaging)
  • Sensitive-path review completed or maintainer-approved waiver recorded — reviewer/approval link/justification: the change adds a 0444 marker at /run/nemoclaw/hermes-api-port that three in-sandbox helpers read, root-owned under privilege separation and owned by the gateway's own user in the same-uid topology, and it widens the reserved-port set in the Shields-sealed managed-startup profile validation from one literal to a range. A maintainer should confirm the marker's trust boundary in both topologies and the reservation change before merge.
  • Non-success, skipped, or missing CI check accepted by maintainer — check name, approval link, and follow-up issue:

Documentation Writer Review

  • Documentation writer subagent reviewed the completed changes
  • Result: docs-updated
  • Evidence: docs/get-started/quickstart-hermes.mdx, docs/reference/commands.mdx, docs/reference/troubleshooting.mdx, docs/deployment/deploy-to-headless-server.mdx, docs/deployment/set-up-mcp-bridge.mdx, docs/security/credential-rotation.mdx, and docs/manage-sandboxes/run-sandboxes.mdx, plus the changed comments, CLI strings, error strings, and test titles across the diff. The subagent reviewed the writing rules and documentation style over ten passes, seven of them on the merged tree. The early passes closed six blocking items, including a sandbox agent rejection message and an onboarding ready summary that still emitted the manifest default, docs that pointed operators at status, which prints no port, a guard that applied the Hermes port to any agent, and unit tests that had begun reading the real sandbox registry. A later pass returned docs-missing with 14 findings, among them a Hermes credential-rotation procedure that still hardcoded 8642, an agent-neutral port guard described as Hermes-only, an override whose failure result was unstated, and three reader docstrings that claimed a root-owned marker in a topology where the gateway itself owns it. All were applied. Two further passes corrected an override consequence that had been stated in the wrong direction and a guard that would have rejected the very --recreate-sandbox command its own error recommends; both corrections were traced through src/lib/onboard/dashboard.ts, src/lib/onboard/agent-dashboard-forward.ts, and src/lib/onboard/sandbox-lifecycle.ts before they were accepted. The final pass reviewed the port-resolution hardening as a fresh change set and confirmed that the narrowed range leaves no page offering an out-of-range value, that a recreate which is not resuming an interrupted session removes the registry row and therefore allocates, and that the range messages agree word for word across TypeScript, shell, and the reference. Its two blocking findings are applied here. Every finding was verified against source before it was applied, and two requests to add explanatory comments were declined and recorded for maintainer review. The last full pass covered 717a9c841; the commits after it change tests, vitest.config.ts, two budget entries, and the two documentation lines and one error string that pass itself requested.
  • Agent: Claude Code

Verification

  • PR description includes a Signed-off-by: line and every commit appears as Verified in GitHub
  • Normal pre-commit, commit-msg, and pre-push hooks passed, or npm run validate:pr passed after refreshing origin/main when hooks were skipped or unavailable
  • Targeted behavior tests pass for the current change set, or tests are marked not applicable above — command/result or justification: npx vitest run --project cli src/lib/onboard/hermes-api-port.test.ts — 22 passed. npx vitest run --project integration test/generate-managed-startup-profile-fixture.test.ts test/mcp-tool-discovery-image-contract.test.ts test/source-architecture.test.ts — 31 passed. npx vitest run --project cli src/lib/onboard/managed-startup-profile.test.ts — 105 passed. npm run validate:pr — exit 0. npm run docs — 0 errors. CI on the preceding head reported all eight cli-test-shards green and all three PR build and direct managed startup jobs passing, and the PR Review Advisor reported 0 blockers.
  • Applicable broad gate passed — npm test for broad runtime/test-harness changes; npm run check for repo-wide validation/coverage changes — command/result: not run locally; left to CI.
  • Quality Gates section completed with required justifications or waivers
  • No secrets, API keys, or credentials committed
  • npm run docs builds without warnings (doc changes only) — npm run docs exits 0 with 0 errors and 2 warnings. Both warnings are pre-existing learn.microsoft.com language-reference links in docs/reference/troubleshooting.mdx that this change does not touch, so the box stays clear rather than claiming a warning-free build.
  • Doc pages follow the style guide (doc changes only)
  • New doc pages include SPDX header and frontmatter (new pages only)

Signed-off-by: Tinson Lai tinsonl@nvidia.com

Summary by CodeRabbit

  • New Features

    • Hermes assigns unique OpenAI-compatible API ports per sandbox from 8642–8652.
    • Added validated overrides, automatic discovery, and persistent port assignments.
    • Forwarding, health checks, onboarding, cloning, and recovery now use each sandbox’s assigned port.
  • Bug Fixes

    • Prevented dashboard/API port conflicts and improved missing-forward recovery.
    • Added safer handling for invalid, unavailable, or unsafe port configuration.
  • Documentation

    • Updated setup, deployment, command reference, quickstart, security, and troubleshooting guidance.

…port

The API port was a fixed host-wide 8642, so a second Hermes sandbox on one
host could never own an inference forward. A snapshot clone therefore received
no inference forward and its gateway restart could not converge.

Allocate the port per sandbox from 8642-8652, persist it on the registry row,
and publish it to the sandbox through the create environment. The entrypoint
re-publishes it as a root-owned read-only marker because a one-shot sandbox
exec does not inherit the supervisor environment. Forward recovery now resolves
manifest-declared ports against the sandbox's own ports instead of demanding
the agent defaults a sibling sandbox already holds.

Signed-off-by: Tinson Lai <tinsonl@nvidia.com>
The first pass left three host-side surfaces on the manifest default: the
`sandbox agent` rejection message, the onboarding ready summary, and the docs.
Each told an operator to forward a port their sandbox does not use.

Relaunch also reached the allocator through the sandbox-create environment. A
sandbox registered before the port became per-sandbox has no recorded value, so
the allocator saw its own live forward holding the default and moved it, leaving
the sandbox bound to one port while the host forwarded another. A registered
sandbox without a port now keeps the default; only an unregistered sandbox takes
a fresh allocation.

Signed-off-by: Tinson Lai <tinsonl@nvidia.com>
…er-sandbox

Signed-off-by: Tinson Lai <tinsonl@nvidia.com>

# Conflicts:
#	ci/source-architecture-budget.json
@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: aa95308b-4b03-406d-a728-5b8151507d6f

📥 Commits

Reviewing files that changed from the base of the PR and between e46d21f and 279f2f4.

📒 Files selected for processing (9)
  • agents/hermes/mcp-config-transaction.py
  • agents/hermes/start.sh
  • docs/deployment/set-up-mcp-bridge.mdx
  • docs/reference/commands.mdx
  • docs/reference/troubleshooting.mdx
  • scripts/install.sh
  • src/lib/agent/onboard.test.ts
  • src/lib/agent/onboard.ts
  • test/hermes-mcp-config-transaction.test.ts
🚧 Files skipped from review as they are similar to previous changes (9)
  • docs/deployment/set-up-mcp-bridge.mdx
  • test/hermes-mcp-config-transaction.test.ts
  • src/lib/agent/onboard.ts
  • src/lib/agent/onboard.test.ts
  • docs/reference/troubleshooting.mdx
  • scripts/install.sh
  • agents/hermes/mcp-config-transaction.py
  • agents/hermes/start.sh
  • docs/reference/commands.mdx

📝 Walkthrough

Walkthrough

Hermes now allocates API ports per sandbox from ports 8642–8652. The selected port is stored, propagated to startup and forwarding, securely published, and used by recovery, cloning, and health checks.

Changes

Hermes API port allocation and propagation

Layer / File(s) Summary
Port contracts and allocation
src/lib/core/*, src/lib/onboard/*, src/lib/state/*
Added range validation, allocation, occupancy tracking, persistence, and dashboard collision checks.
Onboarding and registry propagation
src/lib/onboard/*, src/lib/agent/onboard.ts, src/lib/actions/sandbox/agent/*
Sandbox creation, registration, forwarding, diagnostics, and status rendering now use sandbox-specific API ports.
Recovery and snapshot cloning
src/lib/actions/sandbox/*
Recovery retargets Hermes health probes. Snapshot clones receive independent API ports before creation.
Runtime discovery and health checks
agents/hermes/*, scripts/*
Hermes publishes its API port through a secure marker. Runtime checks and forward restoration resolve the selected port.
Supporting validation and documentation
docs/*, ci/*, test/*
Updated port, forwarding, troubleshooting, reload-phase, marker, image-contract, and architecture-budget coverage.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

  • NVIDIA/NemoClaw#8578: Both changes affect dashboard-forwarding behavior, while this PR adds Hermes API-port resolution.

Suggested reviewers: cjagwani, cv

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 37.93% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the primary change: assigning each Hermes sandbox its own OpenAI-compatible API port.
Linked Issues check ✅ Passed The changes address issue #8543 by allocating and persisting per-sandbox API ports, restoring inference forwards, and covering clone recovery and gateway restart behavior.
Out of Scope Changes check ✅ Passed The implementation, tests, documentation, and validation updates support the per-sandbox Hermes API port and snapshot-forward recovery objectives.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/hermes-api-port-per-sandbox

Comment @coderabbitai help to get the list of available commands.

@github-code-quality

github-code-quality Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Code Coverage Overview

Languages: TypeScript

TypeScript / code-coverage/plugin

The overall coverage in commit 279f2f4 in the fix/hermes-api-port-... branch remains at 96%, unchanged from commit 05d607c in the main branch.


Updated August 10, 2026 02:29 UTC

@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

@laitingsheng laitingsheng added integration: hermes Hermes integration behavior area: sandbox OpenShell sandbox lifecycle, runtime, config, or recovery bug-fix PR fixes a bug or regression feature PR adds or expands user-visible functionality labels Aug 7, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/lib/onboard/dashboard-port.ts (1)

318-344: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Reject explicit Hermes API/dashboard port overlaps before sandbox creation.

assertDashboardPortNotReserved rejects dashboard ports in 8642–8652, but resolveOnboardHermesApiPort accepts any valid NEMOCLAW_HERMES_API_PORT without checking the resolved dashboard port. Thus NEMOCLAW_HERMES_API_PORT=18789 can map both resources to one host port. Reject this equality and add regression tests for both cases.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/onboard/dashboard-port.ts` around lines 318 - 344, Update
resolveOnboardHermesApiPort to reject configurations where the resolved Hermes
API port equals the resolved dashboard port, including explicit
NEMOCLAW_HERMES_API_PORT values and automatically selected ports. Ensure the
rejection occurs before sandbox creation and add regression tests covering both
explicit overlap and overlap produced by resolution.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@agents/hermes/start.sh`:
- Around line 173-196: Use the shared Hermes API-port validity contract of
8642–8652 at all affected sites: update agents/hermes/start.sh lines 173-196,
agents/hermes/mcp-config-transaction.py lines 88-106,
agents/hermes/plugin/__init__.py lines 1035-1051,
scripts/managed-gateway-control.py lines 1125-1151, and scripts/install.sh lines
465-485 to reject values outside that inclusive range while preserving existing
defaults and error handling.

In `@docs/deployment/deploy-to-headless-server.mdx`:
- Line 292: Update all listed documentation examples to reflect dynamic Hermes
API-port allocation: in docs/deployment/deploy-to-headless-server.mdx lines
292-292, qualify 8642 as the first-sandbox default and revise adjacent curl
guidance; in docs/reference/commands.mdx lines 1216-1217, replace fixed-port
wording and update the Hermes agent example to use the port from openshell
forward list; in docs/reference/commands.mdx lines 1918-1919, use the selected
API port for the gateway-token example; and in
docs/reference/troubleshooting.mdx lines 3132-3133, qualify the 8642
troubleshooting path and update its health/client examples accordingly.

In `@docs/reference/troubleshooting.mdx`:
- Around line 3163-3167: Update the troubleshooting guidance around the final
destroy/re-onboard instruction to distinguish new second Hermes sandboxes from
existing pre-change sandboxes. State that new sandboxes receive the next free
API port automatically, while destroy and re-onboard is required only for an
existing sandbox with a registry row lacking hermesApiPort.

In `@src/lib/agent/onboard.ts`:
- Around line 559-577: The API dashboard branch that calls
printAdditionalForwardPorts must pass sandboxName so Hermes resolves its
registered sandbox-specific API port instead of the manifest default. Update
that kind === "api" call site, and add coverage for the Hermes dashboard.kind
=== "api" path verifying the sandbox-specific port is announced.

---

Outside diff comments:
In `@src/lib/onboard/dashboard-port.ts`:
- Around line 318-344: Update resolveOnboardHermesApiPort to reject
configurations where the resolved Hermes API port equals the resolved dashboard
port, including explicit NEMOCLAW_HERMES_API_PORT values and automatically
selected ports. Ensure the rejection occurs before sandbox creation and add
regression tests covering both explicit overlap and overlap produced by
resolution.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: dc739679-b1ab-436c-a70f-c20c3887833e

📥 Commits

Reviewing files that changed from the base of the PR and between 71a7e7c and 525b49e.

📒 Files selected for processing (37)
  • agents/hermes/mcp-config-transaction.py
  • agents/hermes/plugin/__init__.py
  • agents/hermes/start.sh
  • ci/source-architecture-budget.json
  • docs/deployment/deploy-to-headless-server.mdx
  • docs/deployment/set-up-mcp-bridge.mdx
  • docs/get-started/quickstart-hermes.mdx
  • docs/reference/commands.mdx
  • docs/reference/troubleshooting.mdx
  • scripts/install.sh
  • scripts/managed-gateway-control.py
  • src/commands/sandbox/agent.ts
  • src/lib/actions/sandbox/agent/passthrough-help.ts
  • src/lib/actions/sandbox/agent/passthrough.test.ts
  • src/lib/actions/sandbox/agent/passthrough.ts
  • src/lib/actions/sandbox/forward-recovery-declared-ports.test.ts
  • src/lib/actions/sandbox/forward-recovery.ts
  • src/lib/actions/sandbox/process-recovery.ts
  • src/lib/actions/sandbox/snapshot-restore-clone-ports.test.ts
  • src/lib/actions/sandbox/snapshot.ts
  • src/lib/agent/onboard.test.ts
  • src/lib/agent/onboard.ts
  • src/lib/core/ports.ts
  • src/lib/onboard/agent-dashboard-forward.ts
  • src/lib/onboard/dashboard-port.ts
  • src/lib/onboard/hermes-api-port.test.ts
  • src/lib/onboard/hermes-api-port.ts
  • src/lib/onboard/hermes-dashboard.ts
  • src/lib/onboard/managed-startup-profile.test.ts
  • src/lib/onboard/managed-startup/profile.ts
  • src/lib/onboard/preflight-ports.ts
  • src/lib/onboard/sandbox-create-launch.ts
  • src/lib/onboard/sandbox-registration.ts
  • src/lib/state/gateway-registry.ts
  • src/lib/state/registry.ts
  • src/lib/state/registry/types.ts
  • test/hermes-mcp-reload-convergence.test.ts

Comment thread agents/hermes/start.sh Outdated
Comment thread docs/deployment/deploy-to-headless-server.mdx Outdated
Comment thread docs/reference/troubleshooting.mdx Outdated
Comment thread src/lib/agent/onboard.ts
@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

PR Review Advisor — No blocking findings reported

Advisor assessment: No blocking advisor findings reported
Next action: No advisor follow-up needed.
Findings: 0 blockers · 0 warnings · 0 suggestions

Model lanes

  • GPT-5.6 Terra (primary): Completed · medium confidence · 0 blockers · 0 warnings · 0 suggestions
  • Nemotron 3 Ultra (second opinion): Failed after a partial review · low confidence · 0 blockers · 1 warning · 1 suggestion

Second-opinion terminology and E2E selections are advisory. Live E2E does not run automatically for pull requests.

3 semantic terminology decisions

Terminology decisions are advisory. They affect the assessment only when a separate finding identifies concrete semantic impact.

  • justified — sandbox's own API port at docs/deployment/deploy-to-headless-server.mdx:292: Use “sandbox's own API port” when distinguishing the recorded per-sandbox port from a manifest default or sibling port.
  • established — root-separated topology at agents/hermes/plugin/init.py:1040: Retain “root-separated topology” for the existing privilege-separated Hermes runtime arrangement.
  • established — manifest default at src/lib/actions/sandbox/forward-recovery.ts:449: Retain “manifest default” when distinguishing a declared default from sandbox-specific state.

E2E guidance

Advisory only. A maintainer can dispatch the default E2E suite against this exact revision.

Recommended E2E: cloud-inference, cloud-onboard, full-e2e, hermes-e2e, hermes-inference-switch, managed-image-multiarch-startup, managed-image-protected-runtime, security-posture, bedrock-runtime-compatible-anthropic, channels-stop-start, dashboard-remote-bind, device-auth-health, hermes-shields-config, issue-4462-scope-upgrade-approval, onboard-repair, onboard-resume, openclaw-inference-switch, rebuild-openclaw, state-backup-restore

Workflow run details

This automated review informs maintainers. Warnings and suggestions do not require a response. A maintainer decides whether to merge.

@copy-pr-bot

copy-pr-bot Bot commented Aug 7, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@coderabbitai

coderabbitai Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@cv cv left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agents/hermes/start.sh:3319 publishes the trusted Hermes API-port marker only on the root startup path. The supported managed non-root path exits through lines 3254-3284 without creating it. agents/hermes/mcp-config-transaction.py, agents/hermes/plugin/__init__.py, and scripts/managed-gateway-control.py then silently use port 8642. For a sandbox configured for port 8643, a sandbox user can bind 8642 and cause reload or lifecycle probes to accept the wrong listener. Publish a supervisor-owned, sandbox-user-unwritable binding before non-root helpers can run, or make those helpers fail closed when the binding is absent. Add an integration regression test that starts managed non-root Hermes on 8643, verifies the binding ownership and mode, verifies all helpers probe 8643, and proves a listener on 8642 cannot satisfy readiness.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@agents/hermes/plugin/__init__.py`:
- Around line 1048-1051: Update the port-parsing logic around the raw marker
conversion to catch ValueError from int(raw) and return the existing 8642
fallback, including for non-ASCII digit strings accepted by isdigit(). Add a
negative-path test covering such input.

In `@src/lib/onboard/hermes-api-port.ts`:
- Around line 125-154: Update the missing-or-invalid registry-state path used by
restore_onboard_forward_after_post_checks so it reuses the persisted result from
resolveOnboardHermesApiPort instead of falling back to the fixed 8642 port. If
no valid resolved port is available, fail closed rather than starting a watcher
on the default port, and add coverage for a custom hermesApiPort followed by
invalid registry state.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: e00f63d3-7c15-4071-a6d8-692b6001f10a

📥 Commits

Reviewing files that changed from the base of the PR and between 7d1c24e and 38d5b86.

📒 Files selected for processing (37)
  • agents/hermes/mcp-config-transaction.py
  • agents/hermes/plugin/__init__.py
  • agents/hermes/start.sh
  • ci/source-architecture-budget.json
  • docs/deployment/deploy-to-headless-server.mdx
  • docs/deployment/set-up-mcp-bridge.mdx
  • docs/get-started/quickstart-hermes.mdx
  • docs/reference/commands.mdx
  • docs/reference/troubleshooting.mdx
  • scripts/install.sh
  • scripts/managed-gateway-control.py
  • src/commands/sandbox/agent.ts
  • src/lib/actions/sandbox/agent/passthrough-help.ts
  • src/lib/actions/sandbox/agent/passthrough.test.ts
  • src/lib/actions/sandbox/agent/passthrough.ts
  • src/lib/actions/sandbox/forward-recovery-declared-ports.test.ts
  • src/lib/actions/sandbox/forward-recovery.ts
  • src/lib/actions/sandbox/process-recovery.ts
  • src/lib/actions/sandbox/snapshot-restore-clone-ports.test.ts
  • src/lib/actions/sandbox/snapshot.ts
  • src/lib/agent/onboard.test.ts
  • src/lib/agent/onboard.ts
  • src/lib/core/ports.ts
  • src/lib/onboard/agent-dashboard-forward.ts
  • src/lib/onboard/dashboard-port.ts
  • src/lib/onboard/hermes-api-port.test.ts
  • src/lib/onboard/hermes-api-port.ts
  • src/lib/onboard/hermes-dashboard.ts
  • src/lib/onboard/managed-startup-profile.test.ts
  • src/lib/onboard/managed-startup/profile.ts
  • src/lib/onboard/preflight-ports.ts
  • src/lib/onboard/sandbox-create-launch.ts
  • src/lib/onboard/sandbox-registration.ts
  • src/lib/state/gateway-registry.ts
  • src/lib/state/registry.ts
  • src/lib/state/registry/types.ts
  • test/hermes-mcp-reload-convergence.test.ts
🚧 Files skipped from review as they are similar to previous changes (35)
  • src/commands/sandbox/agent.ts
  • src/lib/core/ports.ts
  • src/lib/state/registry.ts
  • src/lib/actions/sandbox/forward-recovery-declared-ports.test.ts
  • docs/deployment/set-up-mcp-bridge.mdx
  • docs/deployment/deploy-to-headless-server.mdx
  • ci/source-architecture-budget.json
  • src/lib/agent/onboard.test.ts
  • src/lib/onboard/managed-startup-profile.test.ts
  • src/lib/onboard/sandbox-registration.ts
  • src/lib/onboard/preflight-ports.ts
  • docs/get-started/quickstart-hermes.mdx
  • src/lib/actions/sandbox/snapshot.ts
  • scripts/managed-gateway-control.py
  • src/lib/state/gateway-registry.ts
  • src/lib/state/registry/types.ts
  • src/lib/actions/sandbox/agent/passthrough.test.ts
  • src/lib/actions/sandbox/process-recovery.ts
  • docs/reference/troubleshooting.mdx
  • src/lib/actions/sandbox/agent/passthrough.ts
  • src/lib/actions/sandbox/snapshot-restore-clone-ports.test.ts
  • src/lib/onboard/managed-startup/profile.ts
  • src/lib/onboard/hermes-dashboard.ts
  • src/lib/onboard/hermes-api-port.test.ts
  • scripts/install.sh
  • src/lib/actions/sandbox/forward-recovery.ts
  • src/lib/actions/sandbox/agent/passthrough-help.ts
  • src/lib/onboard/agent-dashboard-forward.ts
  • agents/hermes/mcp-config-transaction.py
  • agents/hermes/start.sh
  • docs/reference/commands.mdx
  • test/hermes-mcp-reload-convergence.test.ts
  • src/lib/onboard/dashboard-port.ts
  • src/lib/agent/onboard.ts
  • src/lib/onboard/sandbox-create-launch.ts

Comment thread agents/hermes/plugin/__init__.py Outdated
Comment thread src/lib/onboard/hermes-api-port.ts
The profile module is bundled into the managed image and executed directly by
node type stripping, so a relative value import broke both consumers. Declare
the Hermes API port range locally and pin it to core/ports through the profile
test.

Signed-off-by: Tinson Lai <tinsonl@nvidia.com>
Signed-off-by: Tinson Lai <tinsonl@nvidia.com>
The non-root startup path returned before the root-only marker write, so the
same-uid helpers that probe the public relay fell back to the default port. It
now publishes the marker with the identity that runs the gateway and refuses to
start when an allocated port cannot be published. Documentation states the
per-sandbox port where a page still named the default.

Signed-off-by: Tinson Lai <tinsonl@nvidia.com>
Rerunning onboarding with NEMOCLAW_HERMES_API_PORT and without
--recreate-sandbox moves the host forward while the relay keeps the recorded
port, which is the opposite of what the reference stated.

Signed-off-by: Tinson Lai <tinsonl@nvidia.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@agents/hermes/start.sh`:
- Around line 2915-2929: Update both current-user and root API-port marker
publication paths, including publish_hermes_api_port_marker_current_user and the
corresponding root flow, to write the value to a temporary file, apply final
permissions (and root ownership where applicable), then atomically rename it
into place. Remove the successful default-port fallback when cleanup or
replacement fails: if an old marker remains or publication cannot complete, fail
closed with a nonzero result. Add restart coverage for stale markers and
publication failures.
- Around line 3342-3344: Harden the marker publication around
HERMES_API_PORT_MARKER by validating that its parent directory is a real
root-owned, non-writable directory before writing. Replace the direct printf
redirection with a root-created temporary file in that directory, set its
contents and permissions, then atomically rename it to the marker so planted
symlinks cannot be followed; add a negative test covering a pre-existing marker
symlink.

In `@docs/security/credential-rotation.mdx`:
- Around line 219-221: Update the credential-rotation commands to use the
placeholder <api-port> instead of the literal 8642, including forwarding and API
probe commands. Explicitly instruct users to replace <api-port> with the port
shown for their selected <sandbox> by openshell forward list, while preserving
the existing first-sandbox context.

In `@src/lib/onboard/managed-startup-profile.test.ts`:
- Around line 978-986: Update the test around validateManagedStartupProfile to
iterate through every port from HERMES_API_PORT_RANGE_START through
HERMES_API_PORT_RANGE_END inclusively, asserting each reserved port throws.
Preserve or add adjacent-port assertions to confirm ports immediately outside
the declared range remain accepted.

In `@test/hermes-api-port-marker.test.ts`:
- Around line 15-33: Remove both conditionals from
runHermesApiPortMarkerPublication by extracting the runtime-parent and
stale-marker setup variants into named helpers, such as writable, blocked, and
read-only-stale-marker setups. Update the runner to accept and invoke the
selected setup helper so its body remains linear while preserving the existing
test scenarios.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 0f741197-7e5c-4cb9-8f95-03fa38a1feb2

📥 Commits

Reviewing files that changed from the base of the PR and between 38d5b86 and 1b819cc.

📒 Files selected for processing (19)
  • agents/hermes/mcp-config-transaction.py
  • agents/hermes/plugin/__init__.py
  • agents/hermes/start.sh
  • ci/source-architecture-budget.json
  • docs/get-started/quickstart-hermes.mdx
  • docs/manage-sandboxes/run-sandboxes.mdx
  • docs/reference/commands.mdx
  • docs/reference/troubleshooting.mdx
  • docs/security/credential-rotation.mdx
  • scripts/managed-gateway-control.py
  • src/lib/actions/sandbox/agent/passthrough-help.ts
  • src/lib/actions/sandbox/forward-recovery-declared-ports.test.ts
  • src/lib/agent/onboard.test.ts
  • src/lib/agent/onboard.ts
  • src/lib/onboard/managed-startup-profile.test.ts
  • src/lib/onboard/managed-startup/profile.ts
  • test/hermes-api-port-marker.test.ts
  • test/mcp-tool-discovery-image-contract.test.ts
  • tools/mcp-tool-discovery-runtime/reviewed-runtime-bundle/managed-startup-image-runtime.bundle
🚧 Files skipped from review as they are similar to previous changes (12)
  • docs/get-started/quickstart-hermes.mdx
  • src/lib/actions/sandbox/agent/passthrough-help.ts
  • scripts/managed-gateway-control.py
  • src/lib/actions/sandbox/forward-recovery-declared-ports.test.ts
  • docs/reference/troubleshooting.mdx
  • agents/hermes/plugin/init.py
  • src/lib/agent/onboard.ts
  • src/lib/agent/onboard.test.ts
  • src/lib/onboard/managed-startup/profile.ts
  • ci/source-architecture-budget.json
  • agents/hermes/mcp-config-transaction.py
  • docs/reference/commands.mdx

Comment thread agents/hermes/start.sh Outdated
Comment thread agents/hermes/start.sh Outdated
Comment thread docs/security/credential-rotation.mdx Outdated
Comment thread src/lib/onboard/managed-startup-profile.test.ts
Comment thread test/hermes-api-port-marker.test.ts Outdated
Signed-off-by: Carlos Villela <cvillela@nvidia.com>
Comment thread scripts/managed-gateway-control.py Fixed
Comment thread scripts/managed-gateway-control.py Fixed
Signed-off-by: Carlos Villela <cvillela@nvidia.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
test/managed-gateway-control.test.ts (1)

148-157: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Add rejection coverage for the new _hermes_api_port guard.

The fixture now supplies a valid NEMOCLAW_HERMES_API_PORT=8645, and Line 1306 asserts the accepted value. No case supplies a rejected value. _hermes_api_port in scripts/managed-gateway-control.py raises ControlError("GATEWAY_UNSAFE_CONFIG_PATH") for a non-numeric value and for a value outside 8642–8652. That guard exists to reject a tampered supervisor environment, and this file is its only test.

Add a case that rewrites the pid 40 environment with an out-of-range value, calls control._agent_spec("hermes", reader, supervisor), and records the resulting error code.

As per coding guidelines: "Security-sensitive paths, including credential sanitization and SSRF validation, require extra test coverage."

🧪 Proposed additional case
         hermes = control._agent_spec("hermes", reader, supervisor)
+        remove_process(proc_root, 40)
+        write_process(
+            proc_root,
+            namespace_path,
+            40,
+            222,
+            1,
+            1000,
+            b"bash\0/usr/local/bin/nemoclaw-start\0",
+            b"PATH=/usr/bin\0NEMOCLAW_DASHBOARD_PORT=18789\0NEMOCLAW_HERMES_API_PORT=9000\0",
+        )
+        try:
+            control._agent_spec("hermes", reader, control._discover_supervisor(reader))
+            unsafe_api_port = "accepted"
+        except control.ControlError as error:
+            unsafe_api_port = error.code

Restore the pid 40 fixture with the valid value afterwards, then add unsafe_api_port to the printed object and assert "GATEWAY_UNSAFE_CONFIG_PATH".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/managed-gateway-control.test.ts` around lines 148 - 157, Extend the test
around the existing pid 40 fixture and control._agent_spec("hermes", reader,
supervisor) call to rewrite NEMOCLAW_HERMES_API_PORT with an out-of-range value,
capture the resulting ControlError code as unsafe_api_port, and include it in
the printed object with an assertion for "GATEWAY_UNSAFE_CONFIG_PATH". Restore
the valid 8645 environment fixture afterward so existing accepted-value coverage
remains unchanged.

Source: Coding guidelines

🧹 Nitpick comments (2)
agents/hermes/mcp-config-transaction.py (1)

102-112: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

The Hermes API port range is duplicated as bare literals across the Python runtime. No Python module names the range or the default. Each site repeats 8642 and 8652 inline, so a future range change must be found and applied in every copy. A third copy exists in agents/hermes/plugin/__init__.py _hermes_api_port, which repeats the same two literals four times. The stack outline places the authoritative contract in src/lib/core/ports.ts, and no Python counterpart mirrors it.

Introduce named module constants, for example HERMES_API_PORT_MIN, HERMES_API_PORT_MAX, and HERMES_DEFAULT_API_PORT, and reference them at each site.

  • agents/hermes/mcp-config-transaction.py#L102-L112: replace the inline 8642 <= port <= 8652 bound in _parse_gateway_public_port with the named constants, and define those constants next to GATEWAY_INTERNAL_PORT at Line 99.
  • agents/hermes/mcp-config-transaction.py#L1080-L1083: replace the bare return 8642 default in _service_manager_gateway_public_port with the named default constant, and use the same constant for the module-level GATEWAY_PUBLIC_PORT initializer at Line 182.
  • scripts/managed-gateway-control.py#L1136-L1146: replace the bare return 8642 and the port < 8642 or port > 8652 bound in _hermes_api_port with equivalent named constants defined in that script.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@agents/hermes/mcp-config-transaction.py` around lines 102 - 112, Define
shared-named Hermes API port constants next to GATEWAY_INTERNAL_PORT in
agents/hermes/mcp-config-transaction.py, and use them in
_parse_gateway_public_port, _service_manager_gateway_public_port, and the
GATEWAY_PUBLIC_PORT initializer; update agents/hermes/mcp-config-transaction.py
lines 102-112 and 1080-1083 accordingly. Define equivalent constants in
scripts/managed-gateway-control.py and replace the literals in _hermes_api_port
at lines 1136-1146, preserving the existing range validation and default
behavior.
test/hermes-api-port-startup.test.ts (1)

15-24: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Fail fast when a slice anchor is missing.

source.indexOf(...) returns -1 when either anchor text changes in agents/hermes/start.sh. source.slice(start, end) then produces a misleading fragment instead of an explicit error, and the failure surfaces as an unrelated assertion mismatch. The repository already provides extractShellFunction in test/support/hermes-shell-harness.ts, which throws when the pattern does not match. test/hermes-api-port-marker.test.ts uses it.

Either reuse that helper or assert both offsets before slicing.

♻️ Proposed guard
   const start = source.indexOf('NEMOCLAW_CMD=("$@")');
   const end = source.indexOf('\nHERMES="$(command -v hermes)"', start);
+  if (start < 0 || end < 0) {
+    throw new Error("Expected the API port bootstrap block in agents/hermes/start.sh");
+  }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/hermes-api-port-startup.test.ts` around lines 15 - 24, Make the
startup-script extraction fail explicitly when either anchor is missing: update
the setup around NEMOCLAW_CMD and HERMES anchor lookup to reuse
extractShellFunction from the Hermes shell harness, or validate both indexOf
results before calling source.slice. Preserve the existing extracted script
behavior when both anchors are found.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/lib/actions/uninstall/hermes-forward-watcher-installer.test.ts`:
- Around line 79-116: Update the parameterized cases in the resolver test:
rename the existing string-port case to “non-number port,” and add a separate
fractional numeric port case using hermesApiPort 8645.5 with status 1 and empty
output. Keep the test focused on externally observable resolution behavior.

In `@test/hermes-api-port-marker.test.ts`:
- Line 27: Add a negative-test setup helper alongside trustedRuntimePrelude that
makes stat() return an untrusted ownership or mode value, then add a test
exercising prepare_hermes_root_runtime_dir with that prelude. Assert the script
exits non-zero and no marker is published, while preserving the existing
trusted-prelude tests.

In `@test/hermes-api-port-startup.test.ts`:
- Around line 44-62: Add test cases in the “agents/hermes/start.sh API port
allocation” suite for inclusive valid boundaries 8642 and 8652, plus malformed
inputs including a non-numeric value, an empty value, and surrounding
whitespace. Assert each malformed or out-of-range input exits with status 1 and
reports “Invalid NEMOCLAW_HERMES_API_PORT”, while preserving the existing
interior-port success coverage.

In `@test/hermes-mcp-api-port.test.ts`:
- Around line 26-53: Extend the test around _service_manager_gateway_public_port
with an absent-variable case asserting the default port 8642, and an
identity_change case that makes _gateway_identity return a different identity
during revalidation and asserts PermissionError with the “Hermes service-manager
identity changed while reading” message. Include both results in the test’s
collected output and expected object, while preserving the existing accepted and
rejected cases.

In `@test/hermes-mcp-config-transaction.test.ts`:
- Line 1409: Add a test scenario in the probe-related coverage that does not
stub out `_configure_gateway_public_port`, allowing it to raise
`PermissionError` when the port cannot be resolved; assert that `probe()`
propagates the failure and `main` returns exit code 2 with the sanitized error
path, while keeping the existing mock only in scenarios that require successful
port configuration.

In `@test/hermes-plugin-handlers.test.ts`:
- Around line 42-51: Extend the test around _hermes_api_port by replacing the
always-failing open stub with a marker-aware stub for additional cases. Clear
NEMOCLAW_HERMES_API_PORT, return marker contents for 8646, 9000, and not-a-port,
collect marker_results, and assert the accepted value plus 8642 fallback results
as specified while preserving the existing unreadable-marker coverage.

---

Outside diff comments:
In `@test/managed-gateway-control.test.ts`:
- Around line 148-157: Extend the test around the existing pid 40 fixture and
control._agent_spec("hermes", reader, supervisor) call to rewrite
NEMOCLAW_HERMES_API_PORT with an out-of-range value, capture the resulting
ControlError code as unsafe_api_port, and include it in the printed object with
an assertion for "GATEWAY_UNSAFE_CONFIG_PATH". Restore the valid 8645
environment fixture afterward so existing accepted-value coverage remains
unchanged.

---

Nitpick comments:
In `@agents/hermes/mcp-config-transaction.py`:
- Around line 102-112: Define shared-named Hermes API port constants next to
GATEWAY_INTERNAL_PORT in agents/hermes/mcp-config-transaction.py, and use them
in _parse_gateway_public_port, _service_manager_gateway_public_port, and the
GATEWAY_PUBLIC_PORT initializer; update agents/hermes/mcp-config-transaction.py
lines 102-112 and 1080-1083 accordingly. Define equivalent constants in
scripts/managed-gateway-control.py and replace the literals in _hermes_api_port
at lines 1136-1146, preserving the existing range validation and default
behavior.

In `@test/hermes-api-port-startup.test.ts`:
- Around line 15-24: Make the startup-script extraction fail explicitly when
either anchor is missing: update the setup around NEMOCLAW_CMD and HERMES anchor
lookup to reuse extractShellFunction from the Hermes shell harness, or validate
both indexOf results before calling source.slice. Preserve the existing
extracted script behavior when both anchors are found.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 03e2ae30-a1cf-4cea-9ad3-8bb9beb8b5c3

📥 Commits

Reviewing files that changed from the base of the PR and between 1b819cc and e929e2d.

📒 Files selected for processing (19)
  • agents/hermes/mcp-config-transaction.py
  • agents/hermes/plugin/__init__.py
  • agents/hermes/start.sh
  • docs/reference/commands.mdx
  • docs/security/credential-rotation.mdx
  • scripts/install.sh
  • scripts/managed-gateway-control.py
  • src/lib/actions/uninstall/hermes-forward-watcher-installer.test.ts
  • src/lib/onboard/hermes-api-port.test.ts
  • src/lib/onboard/hermes-api-port.ts
  • src/lib/onboard/managed-startup-profile.test.ts
  • src/lib/onboard/sandbox-create-launch.ts
  • src/lib/onboard/sandbox-registration.ts
  • test/hermes-api-port-marker.test.ts
  • test/hermes-api-port-startup.test.ts
  • test/hermes-mcp-api-port.test.ts
  • test/hermes-mcp-config-transaction.test.ts
  • test/hermes-plugin-handlers.test.ts
  • test/managed-gateway-control.test.ts
🚧 Files skipped from review as they are similar to previous changes (9)
  • src/lib/onboard/managed-startup-profile.test.ts
  • src/lib/onboard/sandbox-registration.ts
  • agents/hermes/plugin/init.py
  • src/lib/onboard/hermes-api-port.test.ts
  • scripts/install.sh
  • src/lib/onboard/hermes-api-port.ts
  • agents/hermes/start.sh
  • docs/reference/commands.mdx
  • docs/security/credential-rotation.mdx

Comment thread src/lib/actions/uninstall/hermes-forward-watcher-installer.test.ts
Comment thread test/hermes-api-port-marker.test.ts
Comment thread test/hermes-api-port-startup.test.ts
Comment thread test/hermes-mcp-api-port.test.ts
module.os.lstat = lambda path: (_ for _ in ()).throw(FileNotFoundError(path))
module._gateway_identity = lambda: (123, 456)
module._gateway_has_managed_parent = lambda pid: True
module._configure_gateway_public_port = lambda: None

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Add a case where probe() cannot resolve the port.

This stub removes the only new behavior that probe() gained. probe() now calls _configure_gateway_public_port, which raises PermissionError when the root marker is unavailable or the service-manager environment is unusable. No test proves that probe propagates that failure and that main returns exit code 2.

Add one scenario that leaves _configure_gateway_public_port raising and asserts the sanitized error path.

As per path instructions for **/*.test.{ts,js,mts,mjs,cts,cjs}: "Flag ... broad mocks that bypass the behavior under test."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/hermes-mcp-config-transaction.test.ts` at line 1409, Add a test scenario
in the probe-related coverage that does not stub out
`_configure_gateway_public_port`, allowing it to raise `PermissionError` when
the port cannot be resolved; assert that `probe()` propagates the failure and
`main` returns exit code 2 with the sanitized error path, while keeping the
existing mock only in scenarios that require successful port configuration.

Source: Path instructions

Comment thread test/hermes-plugin-handlers.test.ts
cv and others added 6 commits August 9, 2026 03:35
Signed-off-by: Carlos Villela <cvillela@nvidia.com>
A recreate that is not resuming an interrupted session removes the registry row
first, so it already allocates a free port and destroy is not required. The
conflict error also named a recorded port that a legacy sandbox does not have.

Signed-off-by: Tinson Lai <tinsonl@nvidia.com>
Signed-off-by: Carlos Villela <cvillela@nvidia.com>
Signed-off-by: Carlos Villela <cvillela@nvidia.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (6)
scripts/checks/vitest-project-overlap.mts (1)

46-46: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Use one authoritative installer-test inventory.

The checker and Vitest configuration maintain the same membership manually. This requires synchronized edits and can allow project ownership to drift.

  • scripts/checks/vitest-project-overlap.mts#L46-L46: Derive INSTALLER_INTEGRATION_TESTS from the canonical inventory.
  • vitest.config.ts#L161-L161: Generate the general integration exclusion from the same inventory.
  • vitest.config.ts#L192-L192: Generate the installer-integration inclusion from the same inventory.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/checks/vitest-project-overlap.mts` at line 46, Use one canonical
installer-test inventory and derive all project ownership lists from it. In
scripts/checks/vitest-project-overlap.mts:46-46, define
INSTALLER_INTEGRATION_TESTS from that inventory; in vitest.config.ts:161-161,
generate the general integration exclusion from the same inventory; and in
vitest.config.ts:192-192, generate the installer-integration inclusion from it,
removing duplicated manually maintained membership.

Sources: Coding guidelines, Path instructions

test/hermes-api-port-marker.test.ts (1)

39-50: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Cover the mode half of the runtime-directory guard.

setupUntrustedRuntime reports 1000:1000:755. It exercises only the ownership half of the assertion "must be root-owned with mode 0755". A regression that drops the mode comparison and keeps the owner comparison still passes this file. Add a setup helper that reports 0:0:777 and assert the same non-zero exit and absent marker.

As per path instructions for agents/**: "Require negative-path tests that prove the boundary rejects bypasses."

🛡️ Proposed additional helper and test
+function setupWorldWritableRuntime(
+  _runtimeParent: string,
+  runtimeDir: string,
+  _markerPath: string,
+  _targetPath: string,
+): MarkerSetup {
+  fs.mkdirSync(runtimeDir, { recursive: true });
+  return {
+    shellPrelude: ['stat() { printf "%s\\n" "0:0:777"; }', "chown() { return 0; }"],
+    targetPath: null,
+  };
+}
+  it("refuses a root-owned runtime directory with a permissive mode (`#8543`)", () => {
+    const run = runHermesApiPortMarkerPublication(8645, setupWorldWritableRuntime);
+
+    expect(run.result.status).toBe(1);
+    expect(run.result.stderr).toContain("must be root-owned with mode 0755");
+    expect(run.marker).toBeNull();
+  });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/hermes-api-port-marker.test.ts` around lines 39 - 50, Add a separate
untrusted-runtime setup helper that reports root ownership with mode 0777, then
add a negative-path test using it. Assert the command exits non-zero and the
marker remains absent, covering the mode check independently of the existing
setupUntrustedRuntime ownership case.

Source: Path instructions

agents/hermes/mcp-config-transaction.py (1)

1080-1083: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Consider rejecting an empty NEMOCLAW_HERMES_API_PORT instead of defaulting.

Line 1082 treats an absent variable and a present-but-empty variable the same way and returns 8642. An absent variable is the legacy case. A present-but-empty variable indicates a broken launch and currently resolves to a port that another sandbox may own. Splitting the two conditions keeps the legacy default and fails closed on the malformed case.

♻️ Proposed change
-    if not values or not values[0]:
+    if not values:
         return 8642
+    if not values[0]:
+        raise PermissionError("Hermes service-manager API port is malformed")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@agents/hermes/mcp-config-transaction.py` around lines 1080 - 1083, Update the
port-resolution logic to distinguish an unset NEMOCLAW_HERMES_API_PORT from a
present-but-empty value: preserve the 8642 legacy default only when the variable
is absent, and reject an explicitly empty value with an appropriate error
instead of selecting a port.
test/hermes-api-port-startup.test.ts (2)

44-62: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

No suite exercises the inclusive edges of the 8642-8652 range. The range is re-implemented in the shell startup path, the shell resolver in scripts/install.sh, and the Python resolver. Each suite tests one interior port and one far-outside port, so an off-by-one in any single implementation stays undetected.

  • test/hermes-api-port-startup.test.ts#L44-L62: add 8642 and 8652 as accepted ports, and add the malformed class of non-numeric, empty, and whitespace-padded values to the rejected cases.
  • src/lib/actions/uninstall/hermes-forward-watcher-installer.test.ts#L86-L91: add registry cases for hermesApiPort 8642 and 8652 with status 0, and cases for 8641 and 8653 with status 1.

As per path instructions for agents/**: "Require negative-path tests that prove the boundary rejects bypasses."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/hermes-api-port-startup.test.ts` around lines 44 - 62, Expand
test/hermes-api-port-startup.test.ts#L44-L62 to accept boundary ports 8642 and
8652 and reject non-numeric, empty, and whitespace-padded values. In
src/lib/actions/uninstall/hermes-forward-watcher-installer.test.ts#L86-L91, add
registry cases accepting 8642 and 8652 with status 0 and rejecting 8641 and 8653
with status 1, covering both inclusive boundaries and their bypasses.

Source: Path instructions


16-24: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Fail loudly when the slice anchors are not found.

indexOf returns -1 when agents/hermes/start.sh no longer contains NEMOCLAW_CMD=("$@") or the HERMES= line. source.slice(-1, end) then yields an almost empty fragment, and the generated script fails with an unrelated bash error such as an unbound PUBLIC_PORT. The sibling file test/hermes-api-port-marker.test.ts uses extractShellFunction from test/support/hermes-shell-harness.ts, which throws a named error instead. Reuse that helper, or assert both indices before slicing.

♻️ Proposed guard
   const start = source.indexOf('NEMOCLAW_CMD=("$@")');
   const end = source.indexOf('\nHERMES="$(command -v hermes)"', start);
+  if (start < 0 || end < 0) {
+    throw new Error("Expected the API port bootstrap block in agents/hermes/start.sh");
+  }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/hermes-api-port-startup.test.ts` around lines 16 - 24, Update the test
setup around the source.slice call to fail immediately when either
NEMOCLAW_CMD=("$@") or the HERMES command anchor is missing. Prefer reusing
extractShellFunction from hermes-shell-harness.ts as in
hermes-api-port-marker.test.ts; otherwise assert both indexOf results are
non-negative before slicing and report the missing anchor clearly.
test/hermes-mcp-api-port.test.ts (1)

33-38: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Cover _read_service_manager_environment instead of always replacing it.

Every case replaces _read_service_manager_environment. The real function enforces MAX_SERVICE_MANAGER_ENVIRONMENT_BYTES and converts a missing /proc/<pid>/environ into a PermissionError. Neither guard runs in this file. A regression that removes the size cap passes. Add a case that calls the real function against a temporary file path, or add a case that asserts the oversize and missing-file errors.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/hermes-mcp-api-port.test.ts` around lines 33 - 38, Extend the tests
around _read_service_manager_environment to exercise the real implementation
rather than only the monkeypatched replacement. Add coverage for the
MAX_SERVICE_MANAGER_ENVIRONMENT_BYTES limit and the PermissionError raised when
the environment source is missing, using a temporary file path or equivalent
fixture; keep the existing gateway-port cases unchanged.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@test/hermes-api-port-marker.test.ts`:
- Around line 129-136: Update the fixture execution helper around the result,
marker, mode, and target reads to perform all captures inside a try block, and
move tmpDir cleanup into a finally block so fs operations cannot leak the
temporary directory. Preserve the returned result, marker, mode, and target
values on successful execution.

In `@test/hermes-mcp-api-port.test.ts`:
- Around line 14-15: Add Python tests for _root_gateway_public_port_marker and
_resolve_gateway_public_port, covering marker security validation, precedence
over other port sources, and the root-level error raised when no marker exists.
Keep the tests focused on these behaviors and align them with the existing
Hermes MCP port-resolution coverage.

---

Nitpick comments:
In `@agents/hermes/mcp-config-transaction.py`:
- Around line 1080-1083: Update the port-resolution logic to distinguish an
unset NEMOCLAW_HERMES_API_PORT from a present-but-empty value: preserve the 8642
legacy default only when the variable is absent, and reject an explicitly empty
value with an appropriate error instead of selecting a port.

In `@scripts/checks/vitest-project-overlap.mts`:
- Line 46: Use one canonical installer-test inventory and derive all project
ownership lists from it. In scripts/checks/vitest-project-overlap.mts:46-46,
define INSTALLER_INTEGRATION_TESTS from that inventory; in
vitest.config.ts:161-161, generate the general integration exclusion from the
same inventory; and in vitest.config.ts:192-192, generate the
installer-integration inclusion from it, removing duplicated manually maintained
membership.

In `@test/hermes-api-port-marker.test.ts`:
- Around line 39-50: Add a separate untrusted-runtime setup helper that reports
root ownership with mode 0777, then add a negative-path test using it. Assert
the command exits non-zero and the marker remains absent, covering the mode
check independently of the existing setupUntrustedRuntime ownership case.

In `@test/hermes-api-port-startup.test.ts`:
- Around line 44-62: Expand test/hermes-api-port-startup.test.ts#L44-L62 to
accept boundary ports 8642 and 8652 and reject non-numeric, empty, and
whitespace-padded values. In
src/lib/actions/uninstall/hermes-forward-watcher-installer.test.ts#L86-L91, add
registry cases accepting 8642 and 8652 with status 0 and rejecting 8641 and 8653
with status 1, covering both inclusive boundaries and their bypasses.
- Around line 16-24: Update the test setup around the source.slice call to fail
immediately when either NEMOCLAW_CMD=("$@") or the HERMES command anchor is
missing. Prefer reusing extractShellFunction from hermes-shell-harness.ts as in
hermes-api-port-marker.test.ts; otherwise assert both indexOf results are
non-negative before slicing and report the missing anchor clearly.

In `@test/hermes-mcp-api-port.test.ts`:
- Around line 33-38: Extend the tests around _read_service_manager_environment
to exercise the real implementation rather than only the monkeypatched
replacement. Add coverage for the MAX_SERVICE_MANAGER_ENVIRONMENT_BYTES limit
and the PermissionError raised when the environment source is missing, using a
temporary file path or equivalent fixture; keep the existing gateway-port cases
unchanged.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 50cfb5f3-efcf-4450-b180-af2cbc7818cb

📥 Commits

Reviewing files that changed from the base of the PR and between 1b819cc and bf43b74.

📒 Files selected for processing (26)
  • agents/hermes/mcp-config-transaction.py
  • agents/hermes/plugin/__init__.py
  • agents/hermes/start.sh
  • ci/test-file-size-budget.json
  • docs/reference/commands.mdx
  • docs/reference/troubleshooting.mdx
  • docs/security/credential-rotation.mdx
  • scripts/checks/vitest-project-overlap.mts
  • scripts/install.sh
  • scripts/managed-gateway-control.py
  • src/lib/actions/uninstall/hermes-forward-watcher-installer.test.ts
  • src/lib/onboard/hermes-api-port.test.ts
  • src/lib/onboard/hermes-api-port.ts
  • src/lib/onboard/managed-startup-profile.test.ts
  • src/lib/onboard/sandbox-create-launch.ts
  • src/lib/onboard/sandbox-registration.ts
  • test/hermes-api-port-marker.test.ts
  • test/hermes-api-port-startup.test.ts
  • test/hermes-mcp-api-port.test.ts
  • test/hermes-mcp-config-transaction.test.ts
  • test/hermes-mcp-probe-api-port.test.ts
  • test/hermes-plugin-handlers.test.ts
  • test/install-hermes-forward-restore.test.ts
  • test/install-preflight.test.ts
  • test/managed-gateway-control.test.ts
  • vitest.config.ts
🚧 Files skipped from review as they are similar to previous changes (14)
  • docs/security/credential-rotation.mdx
  • test/hermes-plugin-handlers.test.ts
  • src/lib/onboard/hermes-api-port.test.ts
  • scripts/install.sh
  • agents/hermes/start.sh
  • src/lib/onboard/hermes-api-port.ts
  • scripts/managed-gateway-control.py
  • test/managed-gateway-control.test.ts
  • src/lib/onboard/managed-startup-profile.test.ts
  • src/lib/onboard/sandbox-registration.ts
  • agents/hermes/plugin/init.py
  • docs/reference/commands.mdx
  • src/lib/onboard/sandbox-create-launch.ts
  • docs/reference/troubleshooting.mdx

Comment thread test/hermes-api-port-marker.test.ts Outdated
Comment thread test/hermes-mcp-api-port.test.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@test/hermes-api-port-marker.test.ts`:
- Around line 129-137: Move the try block in the fixture helper to immediately
follow mkdtempSync, enclosing setup, script creation, spawnSync execution,
result capture, and cleanup. Keep the existing finally block with fs.rmSync so
any failure in setup or execution still removes tmpDir.

In `@test/hermes-mcp-api-port.test.ts`:
- Around line 110-135: Make the regular-file cases in the loop invoking
_root_gateway_public_port_marker independent of the test process UID by mocking
os.fstat to retain actual metadata while reporting st_uid and st_gid as 0,
allowing mode, hard-link, and size validation to execute. Leave the symlink case
unchanged so O_NOFOLLOW continues rejecting it before metadata validation.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 018f5f00-0e57-4298-bc04-c8f74325d6a6

📥 Commits

Reviewing files that changed from the base of the PR and between bf43b74 and 5a02138.

📒 Files selected for processing (7)
  • docs/reference/commands.mdx
  • docs/reference/troubleshooting.mdx
  • src/lib/actions/sandbox/forward-recovery.ts
  • test/hermes-api-port-marker.test.ts
  • test/hermes-api-port-startup.test.ts
  • test/hermes-mcp-api-port.test.ts
  • test/hermes-plugin-handlers.test.ts
🚧 Files skipped from review as they are similar to previous changes (5)
  • docs/reference/troubleshooting.mdx
  • src/lib/actions/sandbox/forward-recovery.ts
  • docs/reference/commands.mdx
  • test/hermes-api-port-startup.test.ts
  • test/hermes-plugin-handlers.test.ts

Comment thread test/hermes-api-port-marker.test.ts Outdated
Comment thread test/hermes-mcp-api-port.test.ts
Signed-off-by: Tinson Lai <tinsonl@nvidia.com>
Signed-off-by: Tinson Lai <tinsonl@nvidia.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/lib/onboard/agent-dashboard-forward.test.ts (1)

41-70: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Make the regression assertion independent of forwarding order.

The toHaveBeenNthCalledWith assertions couple this test to the current forwarding order and array order. The negative toHaveBeenCalledWith(..., expect.anything()) assertion can miss a default-port call when the call has no third argument. Capture the forwarded URLs, assert that 8643 is present and 8642 is absent, and use expect.objectContaining for the allocated-port options.

Proposed diff
+    const forwardedUrls: string[] = [];
     const ensureDashboardForward = vi.fn((_sandboxName, chatUiUrl = "http://127.0.0.1:18789") => {
+      forwardedUrls.push(chatUiUrl);
       const parsed = new URL(chatUiUrl);
       return Number(parsed.port);
     });
...
-    expect(ensureDashboardForward).toHaveBeenNthCalledWith(1, "hm", "http://127.0.0.1:18789", {
-      preserveSandboxPorts: [18789, 8643, 3978],
-    });
-    expect(ensureDashboardForward).toHaveBeenNthCalledWith(2, "hm", "http://127.0.0.1:8643", {
-      preserveSandboxPorts: [18789, 8643, 3978],
-      allowPortReallocation: false,
-    });
-    expect(ensureDashboardForward).not.toHaveBeenCalledWith(
-      "hm",
-      "http://127.0.0.1:8642",
-      expect.anything(),
+    expect(forwardedUrls).toContain("http://127.0.0.1:8643");
+    expect(forwardedUrls).not.toContain("http://127.0.0.1:8642");
+    expect(ensureDashboardForward).toHaveBeenCalledWith(
+      "hm",
+      "http://127.0.0.1:8643",
+      expect.objectContaining({
+        preserveSandboxPorts: expect.arrayContaining([18789, 8643, 3978]),
+        allowPortReallocation: false,
+      }),
     );

As per path instructions: Review tests for behavioral confidence rather than implementation lock-in, and prefer observable outcomes through the public boundary over mock-call assertions.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/onboard/agent-dashboard-forward.test.ts` around lines 41 - 70, Update
the assertions around ensureAgentDashboardForward and ensureDashboardForward to
capture forwarded URLs from the mock calls, then assert that port 8643 is
present and 8642 is absent without relying on call order. Match allocation
options with expect.objectContaining, and ensure the absence check also covers
calls without a third argument.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/lib/onboard/agent-dashboard-forward.test.ts`:
- Around line 41-70: Update the assertions around ensureAgentDashboardForward
and ensureDashboardForward to capture forwarded URLs from the mock calls, then
assert that port 8643 is present and 8642 is absent without relying on call
order. Match allocation options with expect.objectContaining, and ensure the
absence check also covers calls without a third argument.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: de93ff21-7ce4-4e4c-b80d-401587618776

📥 Commits

Reviewing files that changed from the base of the PR and between e869820 and ca256de.

📒 Files selected for processing (2)
  • docs/deployment/deploy-to-headless-server.mdx
  • src/lib/onboard/agent-dashboard-forward.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • docs/deployment/deploy-to-headless-server.mdx

Signed-off-by: Tinson Lai <tinsonl@nvidia.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@test/hermes-mcp-api-port.test.ts`:
- Around line 277-280: Replace the mkdtemp-based absent-marker setup in the
probe test with a tempfile.TemporaryDirectory context, and execute module.main()
within that context so the temporary directory is removed even when SystemExit
is raised.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 9ab234f4-d08a-4854-b8b7-081ff3907931

📥 Commits

Reviewing files that changed from the base of the PR and between ca256de and 98d4595.

📒 Files selected for processing (2)
  • test/hermes-api-port-marker.test.ts
  • test/hermes-mcp-api-port.test.ts

Comment on lines +277 to +280
module.GATEWAY_PUBLIC_PORT_PATH = str(pathlib.Path(tempfile.mkdtemp()) / "absent-marker")
module.os.geteuid = lambda: 0
sys.argv = ["mcp-config-transaction.py", "probe"]
raise SystemExit(module.main())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Clean up the temporary directory.

Line 277 calls tempfile.mkdtemp(), but Line 280 exits the child process through SystemExit without removing the directory. Repeated test runs leave stale directories in the system temporary area. Use tempfile.TemporaryDirectory() and keep module.main() inside its context.

Proposed cleanup
- module.GATEWAY_PUBLIC_PORT_PATH = str(pathlib.Path(tempfile.mkdtemp()) / "absent-marker")
- module.os.geteuid = lambda: 0
- sys.argv = ["mcp-config-transaction.py", "probe"]
- raise SystemExit(module.main())
+ with tempfile.TemporaryDirectory() as temp_dir:
+     module.GATEWAY_PUBLIC_PORT_PATH = str(pathlib.Path(temp_dir) / "absent-marker")
+     module.os.geteuid = lambda: 0
+     sys.argv = ["mcp-config-transaction.py", "probe"]
+     raise SystemExit(module.main())
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
module.GATEWAY_PUBLIC_PORT_PATH = str(pathlib.Path(tempfile.mkdtemp()) / "absent-marker")
module.os.geteuid = lambda: 0
sys.argv = ["mcp-config-transaction.py", "probe"]
raise SystemExit(module.main())
with tempfile.TemporaryDirectory() as temp_dir:
module.GATEWAY_PUBLIC_PORT_PATH = str(pathlib.Path(temp_dir) / "absent-marker")
module.os.geteuid = lambda: 0
sys.argv = ["mcp-config-transaction.py", "probe"]
raise SystemExit(module.main())
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/hermes-mcp-api-port.test.ts` around lines 277 - 280, Replace the
mkdtemp-based absent-marker setup in the probe test with a
tempfile.TemporaryDirectory context, and execute module.main() within that
context so the temporary directory is removed even when SystemExit is raised.

@cv cv left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The commit under review is 98d4595d1.

The previous security finding is resolved. The root-separated path publishes the API port in a root-owned marker with an atomic file replacement. Helpers in the same-user-ID topology read the port from the verified service-manager environment. These helpers fail closed if they cannot verify that source.

Two blocking findings remain:

  1. Correct Hermes API port selection in the headless procedure. docs/deployment/deploy-to-headless-server.mdx:293 tells the reader to use the headless-agent row. Hermes can have dashboard and API port-forward rows for the same sandbox. The example at line 298 then sets API_PORT=8642, which discards the selected value. For another Hermes sandbox, this can send the bearer token to a sibling sandbox or a non-OpenShell listener. Tell the reader to select the headless-agent row whose port is from 8642 through 8652. Tell the reader to replace the example value with that port.

  2. Provide passing managed-runtime activation evidence for Hermes. The PR exact all-agent managed runtime activation check failed during Hermes onboarding for five consecutive PR commits. The latest completed run reports Managed bootstrap Docker supervisor did not reconnect and an indeterminate cutover state: https://github.com/NVIDIA/NemoClaw/actions/runs/31318932707/job/93261035150. GitHub Actions skipped this check for the latest PR commit because OpenClaw direct startup failed first. Concurrent PRs pass the same check, so the evidence does not identify a repository-wide failure. Correct the branch-specific failure and provide a passing result for the latest PR commit.

After you update the PR, rerun the documentation writer review. Refresh the receipt metadata because it identifies commit bf43b741e and does not cover the commit under review.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: sandbox OpenShell sandbox lifecycle, runtime, config, or recovery bug-fix PR fixes a bug or regression feature PR adds or expands user-visible functionality integration: hermes Hermes integration behavior

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Ubuntu 24.04][Sandbox] snapshot clone cannot restore its inference host forward

3 participants